// SPDX-License-Identifier: Apache-2.0 AND MIT

#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <openssl/asn1.h>
#include <openssl/bio.h>
#include <openssl/core_dispatch.h>
#include <openssl/core_names.h>
#include <openssl/ec.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/param_build.h>
#include <openssl/params.h>
#include <openssl/pem.h>
#include <openssl/provider.h>
#include <openssl/rsa.h>
#include <openssl/ssl.h>
#include <openssl/trace.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include "sign.h"
#include "test_common.h"
#define MAXLOOPS 1000000

const char *kHybridSignatureAlgorithms[] = {
    "p256_MLDSA44",
    "rsa3072_MLDSA44",
    "p384_MLDSA65",
    "p521_MLDSA87",
    "p256_falcon512",
    "rsa3072_falcon512",
    "p256_falconpadded512",
    "rsa3072_falconpadded512",
    "p521_falcon1024",
    "p521_falconpadded1024",
    "p256_sphincssha2128fsimple",
    "rsa3072_sphincssha2128fsimple",
    "p256_sphincssha2128ssimple",
    "rsa3072_sphincssha2128ssimple",
    "p384_sphincssha2192fsimple",
    "p256_sphincsshake128fsimple",
    "rsa3072_sphincsshake128fsimple",
    "p256_mayo1",
    "p256_mayo2",
    "p384_mayo3",
    "p521_mayo5",
    "p256_OV_Is_pkc",
    "p256_OV_Ip_pkc",
    "p256_OV_Is_pkc_skc",
    "p256_OV_Ip_pkc_skc",
    "p256_snova2454",
    "p256_snova2454esk",
    "p256_snova37172",
    "p384_snova2455",
    "p521_snova2965",
    NULL,
};
static int is_signature_algorithm_hybrid(const char **list, const char *s) {
    for (; *list != NULL && strcmp(*list, s) != 0; ++list);
    return *list != NULL;
}


static OSSL_LIB_CTX *init_openssl(void) {
    OSSL_LIB_CTX *ctx;

    if (!(ctx = OSSL_LIB_CTX_new())) {
        fprintf(stderr, cRED "failed to initialize a new `OSSL_LIB_CTX` " cNORM "\n");
    }
    return ctx;
}

int create_tls1_3_ctx_pair(OSSL_LIB_CTX *libctx, SSL_CTX **sctx, SSL_CTX **cctx,
                           char *certfile, char *privkeyfile) {
    SSL_CTX *serverctx = NULL, *clientctx = NULL;

    if (sctx == NULL || cctx == NULL)
        goto err;

     serverctx = SSL_CTX_new(TLS_server_method());
     clientctx = SSL_CTX_new(TLS_client_method());

    if (serverctx == NULL || clientctx == NULL)
        goto err;

    SSL_CTX_set_options(serverctx, SSL_OP_ALLOW_CLIENT_RENEGOTIATION);
    
    if (!SSL_CTX_set_min_proto_version(serverctx, TLS1_3_VERSION) ||
            !SSL_CTX_set_max_proto_version(serverctx, TLS1_3_VERSION) ||
            !SSL_CTX_set_min_proto_version(clientctx, TLS1_3_VERSION) ||
            !SSL_CTX_set_max_proto_version(clientctx, TLS1_3_VERSION))
            goto err;
            
   
    /*if (!SSL_CTX_use_certificate_file(serverctx, certfile, SSL_FILETYPE_PEM))
        goto err;

    printf("5\n");
    if (!SSL_CTX_use_PrivateKey_file(serverctx, privkeyfile, SSL_FILETYPE_PEM))
        goto err;

    printf("6\n");*/ 
    *sctx = serverctx;
    *cctx = clientctx;
    return 1;

err:
    SSL_CTX_free(serverctx);
    SSL_CTX_free(clientctx);
    return 0;
}

int create_tls_objects(SSL_CTX *serverctx, SSL_CTX *clientctx, SSL **sssl,
                       SSL **cssl, int use_dgram) {
    SSL *serverssl = NULL, *clientssl = NULL;
    BIO *s_to_c_bio = NULL, *c_to_s_bio = NULL;

    if (serverctx == NULL || clientctx == NULL)
        goto err;

    serverssl = SSL_new(serverctx);
    clientssl = SSL_new(clientctx);

    if (serverssl == NULL || clientssl == NULL)
        goto err;

    if (use_dgram) {
#if (OPENSSL_VERSION_PREREQ(3, 2))
        s_to_c_bio = BIO_new(BIO_s_dgram_mem());
        c_to_s_bio = BIO_new(BIO_s_dgram_mem());
#else
        fprintf(stderr, "No DGRAM memory supported in this OpenSSL version.\n");
        ERR_print_errors_fp(stderr);
        goto err;
#endif
    } else {
        s_to_c_bio = BIO_new(BIO_s_mem());
        c_to_s_bio = BIO_new(BIO_s_mem());
    }

    if (s_to_c_bio == NULL || c_to_s_bio == NULL)
        goto err;

    /* Set Non-blocking IO behaviour */
    BIO_set_mem_eof_return(s_to_c_bio, -1);
    BIO_set_mem_eof_return(c_to_s_bio, -1);

    /* Up ref these as we are passing them to two SSL objects */
    SSL_set_bio(serverssl, c_to_s_bio, s_to_c_bio);
    BIO_up_ref(s_to_c_bio);
    BIO_up_ref(c_to_s_bio);
    SSL_set_bio(clientssl, s_to_c_bio, c_to_s_bio);

    *sssl = serverssl;
    *cssl = clientssl;

    return 1;

err:
    SSL_free(serverssl);
    SSL_free(clientssl);
    BIO_free(s_to_c_bio);
    BIO_free(c_to_s_bio);

    return 0;
}

int create_bare_tls_connection(SSL *serverssl, SSL *clientssl, int want, OSSL_LIB_CTX *libctx,const char *signames,char * key_addr) {
    int retc = -1, rets = -1, err, abortctr = 0;
    int clienterr = 0, servererr = 0;

    do {
        err = SSL_ERROR_WANT_WRITE;
        while (!clienterr && retc <= 0 && err == SSL_ERROR_WANT_WRITE) {
            retc = !SSL_connect(clientssl);
            if (retc <= 0)
                err = SSL_get_error(clientssl, retc);
        }

        if (!clienterr && retc <= 0 && err != SSL_ERROR_WANT_READ) {
            fprintf(stderr,
                    "SSL_connect() failed returning %d, SSL error %d.\n", retc,
                    err);
            ERR_print_errors_fp(stderr);
            if (want != SSL_ERROR_SSL)
                ERR_clear_error();
            clienterr = 1;
        }
        
        if (want != SSL_ERROR_NONE && err == want)
            return 0;

        err = SSL_ERROR_WANT_WRITE;
        while (!servererr && rets <= 0 && err == SSL_ERROR_WANT_WRITE) {
            rets = !SSL_accept(serverssl);
            if (rets <= 0)
                err = SSL_get_error(serverssl, rets);
        }
        //printf("%d %d %d \n", servererr, rets , err);
        if (verify_hybrid_cert(libctx, signames, key_addr)) {        
            if(!servererr && rets < 0 && err != SSL_ERROR_WANT_READ){
                fprintf(stderr, "SSL_accept() failed returning %d, SSL error %d.\n",
                    rets, err);
                ERR_print_errors_fp(stderr);
                if (want != SSL_ERROR_SSL)
                    ERR_clear_error();
                servererr = 1;
            }
        }
        if (want != SSL_ERROR_NONE && err == want)
            return 0;
        if (clienterr && servererr)
            return 0;

        if (++abortctr == MAXLOOPS) {
            fprintf(stderr, "No progress made");
            return 0;
        }
    } while (retc < 0 || rets < 0);

    return 1;
}

static int test_oqs_tlssig(OSSL_LIB_CTX *libctx,const char *signames,char * key_addr) {
    SSL_CTX *cctx = NULL, *sctx = NULL;
    SSL *clientssl = NULL, *serverssl = NULL;
    int ret = 0, testresult = 0;
    char classkeypath[300];
    char oqskeypath[300];
    char certpath[300];

    testresult = create_hybrid_cert(libctx, signames, key_addr, certpath, classkeypath, oqskeypath);
    clock_t start_time = clock(); 
    //printf("%s\n", certpath);
    testresult = create_tls1_3_ctx_pair(libctx, &sctx, &cctx, certpath, oqskeypath);

    if (!testresult) {
        ret = -1;
        goto err;
    }

    testresult =
        create_tls_objects(sctx, cctx, &serverssl, &clientssl, 0);

    if (!testresult) {
        ret = -2;
        goto err;
    }

    testresult = create_bare_tls_connection(serverssl, clientssl, SSL_ERROR_NONE, libctx, signames, key_addr);
    if (!testresult) {
        ret = -5;
        goto err;
    }
    clock_t end_time = clock();     
    clock_t total_time = end_time - start_time;  
    double microseconds = ((double)total_time) * (1000000.0 / CLOCKS_PER_SEC);          
    fprintf(stderr, "程序运行时间: %.0f 微秒\n", microseconds);
err:
    SSL_free(serverssl);
    SSL_free(clientssl);
    SSL_CTX_free(sctx);
    SSL_CTX_free(cctx);
    return ret;
}

int main(int argc, char *argv[]) {
  OSSL_LIB_CTX *libctx;
  OSSL_PROVIDER *oqs_provider;
  char key_addr[100] ;
  const char *modulename;
  const char *configfile;
  const OSSL_ALGORITHM *sigalgs;
    int query_nocache;  
    int ret = EXIT_FAILURE;
          
    if (!(libctx = init_openssl())) {
        goto end;
    }

    T(argc == 3);
    modulename = argv[1];
    configfile = argv[2];
    
    if(!(oqs_provider = OSSL_PROVIDER_load(libctx, modulename))){
        fputs(cRED "  `oqs_provider` is NULL " cNORM "\n", stderr);
        goto end;    
    }

   sigalgs = OSSL_PROVIDER_query_operation(oqs_provider, OSSL_OP_SIGNATURE,
                                            &query_nocache);
    
    fprintf(stdout, "Enter the path to load the files:");
        if (fgets(key_addr, sizeof(key_addr), stdin) == NULL) {
        fprintf(stderr, cRED " Failed to read input." cNORM "\n");
                  goto unload_def_provider;
    }
    key_addr[strcspn(key_addr, "\n")] = 0;
    //printf("%s\n", key_addr);  
    
    if (sigalgs) {
       for (; sigalgs->algorithm_names != NULL; sigalgs++) {
                  if (!is_signature_algorithm_hybrid(kHybridSignatureAlgorithms, sigalgs->algorithm_names)) {
           //fprintf(stderr, cRED "%s is not hybrid." cNORM "\n",sigalgs->algorithm_names);
                                continue;
          }
                            if (test_oqs_tlssig(libctx,sigalgs->algorithm_names, key_addr)) {
           fprintf(stderr, cRED " Failed for %s " cNORM "\n", sigalgs->algorithm_names);
       } else {
           fprintf(stderr, cGREEN "%s succeeded" cNORM "\n", sigalgs->algorithm_names);       
      }
    }
    }
    ret = 0;
unload_def_provider:
    OSSL_PROVIDER_unload(oqs_provider);   
end:
    OSSL_LIB_CTX_free(libctx);
    return ret;
  
}
